import re
import pandas as pd


# ====================== 物理模型规范函数 ======================
def physical_table_english_first_char(name):
    """(1)物理表英文名称首字符只能为英文字母或下划线'_'"""
    return len(name) > 0 and (name[0].isalpha() or name[0] == '_')


def physical_table_english_chars(name):
    """(2)物理表英文名称中只能包含下划线'_'、数字、字母"""
    return all(c.isalnum() or c == '_' for c in name)


def physical_table_english_no_keywords(name, db_keywords):
    """(3)物理表英文名称禁止直接使用数据库关键字或保留字"""
    return name.upper() not in db_keywords


def physical_table_english_length(name):
    """(4)物理表英文名称长度建议不超过30个字符"""
    return len(name) <= 30


def physical_table_english_no_invisible(name):
    """(5)物理表英文名称不允许出现空格、换行符等不可见字符"""
    return not any(c.isspace() for c in name)


def physical_table_english_unique(name, existing_names):
    """(6)物理表英文名称不允许重复"""
    return name not in existing_names


def physical_table_chinese_length(name):
    """(7)物理表中文名称建议不超过15个汉字"""
    return len(name) <= 15


def physical_table_chinese_phrase_type(name):
    """(8)物理表中文名称应使用名词或者动宾短语"""
    return not name.endswith(('了', '着', '过'))


def physical_table_chinese_no_special_chars(name):
    """(9)物理表中文名称不应出现特殊字符"""
    return not re.search(r'[`~!@#$%^&*\\+=|{}\'\'\[\]<>/?~！￥%……《》——【】‘”“’？]', name)


def physical_table_chinese_no_invisible(name):
    """(10)物理表中文名称不允许出现空格、换行符等不可见字符"""
    return not any(c.isspace() for c in name)


def physical_table_chinese_not_empty(name):
    """(11)物理表中文名称不允许为空"""
    return len(name) > 0


def physical_table_chinese_unique(name, existing_names):
    """(12)物理表中文名称不允许重复"""
    return name not in existing_names


def field_english_first_char(name):
    """(13)字段英文名称首字符只能为英文字母或下划线'_'"""
    return len(name) > 0 and (name[0].isalpha() or name[0] == '_')


def field_english_chars(name):
    """(14)字段英文名称中只能包含下划线'_'、数字、字母"""
    return all(c.isalnum() or c == '_' for c in name)


def field_english_length(name):
    """(15)字段英文名称长度建议不超过30个字符"""
    return len(name) <= 30


def field_english_no_invisible(name):
    """(16)字段英文名称不允许出现空格、换行符等不可见字符"""
    return not any(c.isspace() for c in name)


def field_english_no_keywords(name, db_keywords):
    """(17)字段英文名称禁止直接使用数据库关键字或保留字"""
    return name.upper() not in db_keywords


def field_english_not_empty(name):
    """(18)字段英文名称不允许为空"""
    return len(name) > 0


def field_english_unique_in_table(name, existing_names):
    """(19)同一物理表，字段英文名称不允许重复"""
    return name not in existing_names


def field_english_consistent_across_tables(name, standard_name):
    """(20)相同业务含义的字段，其英文名称应保持一致"""
    return name == standard_name


def field_chinese_length(name):
    """(21)字段中文名称不应超过15个汉字"""
    return len(name) <= 15


def field_chinese_phrase_type(name):
    """(22)字段中文名称应使用名词或者动宾短语"""
    return not name.endswith(('了', '着', '过'))


def field_chinese_not_empty(name):
    """(23)字段中文名称不允许为空"""
    return len(name) > 0


def field_chinese_unique_in_table(name, existing_names):
    """(24)同一物理表，字段中文名称不允许重复"""
    return name not in existing_names


def field_chinese_no_special_chars(name):
    """(25)字段中文名称不应出现特殊字符"""
    return not re.search(r'[`~!@#$%^&*\\+=|{}\'\'\[\]<>/?~！￥%……《》——【】‘”“’？]', name)


def field_chinese_no_invisible(name):
    """(26)字段中文名称不允许出现空格、换行符等不可见字符"""
    # 检查所有Unicode空白字符类别
    if any(c.isspace() for c in name):
        return False

    # 扩展检查其他不可见字符
    invisible_chars = {
        '\u200b', '\u200c', '\u200d', '\u2060', '\ufeff',  # 零宽字符
        '\u3000',  # 全角空格
        '\u00A0',  # 不换行空格
        '\u2028', '\u2029'  # 行分隔符/段落分隔符
    }
    return not any(c in invisible_chars for c in name)


def field_chinese_consistent_across_tables(name, standard_name):
    """(27)相同业务含义的字段，其中文名称应保持一致"""
    return name == standard_name


def numeric_data_type_should_be_numeric(field_type):
    """(28)数值类型检查 - 确保数值类型使用NUMERIC/NUMBER/DECIMAL"""
    field_type = field_type.upper().strip()
    # 检查是否为常见数值类型
    if field_type in ('INT', 'INTEGER', 'FLOAT', 'DOUBLE', 'SMALLINT', 'BIGINT'):
        return False
    # 检查是否为标准数值类型
    if field_type.startswith(('NUMERIC', 'NUMBER', 'DECIMAL')):
        # 验证数值类型的精度和长度格式
        if '(' in field_type:
            try:
                params = field_type[field_type.index('(') + 1:field_type.index(')')].split(',')
                precision = int(params[0].strip())
                scale = int(params[1].strip()) if len(params) > 1 else 0
                return 1 <= precision <= 38 and 0 <= scale <= precision
            except (ValueError, IndexError):
                return False
        return True
    return False


def date_data_should_be_timestamp(field_type):
    """(28.1)日期类型检查 - 确保日期类型使用TIMESTAMP"""
    field_type = field_type.upper().strip()
    # 检查是否为常见日期类型
    if field_type in ('DATE', 'TIME', 'DATETIME'):
        return False
    # 检查是否为标准时间戳类型
    if field_type.startswith('TIMESTAMP'):
        # 验证时区设置
        if 'WITH TIME ZONE' in field_type or 'TZ' in field_type:
            return True
        return True
    return False


def long_text_should_use_text_type(field_type, length):
    """(28.2)长文本检查 - VARCHAR长度超过2000应使用TEXT"""
    field_type = field_type.upper().strip()
    # 检查是否为VARCHAR类型
    if field_type.startswith('VARCHAR'):
        try:
            # 提取VARCHAR长度
            if '(' in field_type and ')' in field_type:
                max_length = int(field_type[field_type.index('(') + 1:field_type.index(')')])
                return max_length <= 2000
            # 如果没有指定长度，默认允许
            return True
        except ValueError:
            return False
    # 检查是否为CHAR类型
    if field_type.startswith('CHAR'):
        try:
            if '(' in field_type and ')' in field_type:
                max_length = int(field_type[field_type.index('(') + 1:field_type.index(')')])
                return max_length <= 2000
            return True
        except ValueError:
            return False
    return True


def avoid_large_field_types(field_type):
    """(28.3)大字段类型检查 - 避免使用大字段类型"""
    field_type = field_type.upper().strip()
    # 检查是否为常见大字段类型
    large_types = {
        'TEXT', 'JSON', 'JSONB', 'XML', 'BYTEA', 'BLOB',
        'CLOB', 'LONGTEXT', 'MEDIUMTEXT', 'TINYTEXT',
        'LONGBLOB', 'MEDIUMBLOB', 'TINYBLOB'
    }
    # 检查完整类型名称
    base_type = field_type.split('(')[0].split()[0]
    return base_type not in large_types


# 移除规范28相关函数及调用

# ====================== 逻辑模型规范函数 ======================
def entity_english_camel_case(name):
    """(29)实体英文名称采用驼峰命名规则，首字母大写"""
    if not name:
        return False
    return name == name[0].upper() + name[1:] and '_' not in name


def entity_english_chars(name):
    """(30)实体英文名称中只能包含数字、字母"""
    return name.isalnum()


def entity_english_no_keywords(name, db_keywords):
    """(31)实体英文名称禁止直接使用数据库关键字或保留字"""
    return name.upper() not in db_keywords


def entity_english_length(name):
    """(32)实体英文名称长度建议不超过30个字符"""
    return len(name) <= 30


def entity_english_no_invisible(name):
    """(33)实体英文名称不允许出现空格、换行符等不可见字符"""
    return not any(c.isspace() for c in name)


def entity_english_unique(name, existing_names):
    """(34)实体英文名称不允许重复"""
    return name not in existing_names


def entity_chinese_length(name):
    """(35)实体中文名称建议不超过15个汉字"""
    return len(name) <= 15


def entity_chinese_phrase_type(name):
    """(36)实体中文名称应使用名词或者动宾短语"""
    return not name.endswith(('了', '着', '过'))


def entity_chinese_no_special_chars(name):
    """(37)实体中文名称不应出现特殊字符"""
    return not re.search(r'[`~!@#$%^&*\\+=|{}\'\'\[\]<>/?~！￥%……《》——【】‘”“’？]', name)


def entity_chinese_no_invisible(name):
    """(38)实体中文名称不允许出现空格、换行符等不可见字符"""
    return not any(c.isspace() for c in name)


def entity_chinese_not_empty(name):
    """(39)实体中文名称不允许为空"""
    return len(name) > 0


def entity_chinese_unique(name, existing_names):
    """(40)实体中文名称不允许重复"""
    return name not in existing_names


def attribute_english_first_char(name):
    """(41)属性英文名称首字符只能为英文字母或下划线'_'"""
    return len(name) > 0 and (name[0].isalpha() or name[0] == '_')


def attribute_english_chars(name):
    """(42)属性英文名称中只能包含下划线'_'、数字、字母"""
    return all(c.isalnum() or c == '_' for c in name)


def attribute_english_length(name):
    """(43)属性英文名称长度建议不超过30个字符"""
    return len(name) <= 30


def attribute_english_no_invisible(name):
    """(44)属性英文名称不允许出现空格、换行符等不可见字符"""
    return not any(c.isspace() for c in name)


def attribute_english_no_keywords(name, db_keywords):
    """(45)属性英文名称禁止直接使用数据库关键字或保留字"""
    return name.upper() not in db_keywords


def attribute_english_not_empty(name):
    """(46)属性英文名称不允许为空"""
    return len(name) > 0


def attribute_english_unique_in_entity(name, existing_names):
    """(47)同一实体，属性英文名称不允许重复"""
    return name not in existing_names


def attribute_english_consistent_across_entities(name, standard_name):
    """(48)相同业务含义的属性，其英文名称应保持一致"""
    return name == standard_name


def attribute_chinese_length(name):
    """(49)属性中文名称不应超过15个汉字"""
    return len(name) <= 15


def attribute_chinese_phrase_type(name):
    """(50)属性中文名称应使用名词或者动宾短语"""
    return not name.endswith(('了', '着', '过'))


def attribute_chinese_not_empty(name):
    """(51)属性中文名称不允许为空"""
    return len(name) > 0


def attribute_chinese_unique_in_entity(name, existing_names):
    """(52)同一实体，属性中文名称不允许重复"""
    return name not in existing_names


def attribute_chinese_no_special_chars(name):
    """(53)属性中文名称不应出现特殊字符"""
    return not re.search(r'[`~!@#$%^&*\\+=|{}\'\'\[\]<>/?~！￥%……《》——【】‘”“’？]', name)


def attribute_chinese_no_invisible(name):
    """(54)属性中文名称不允许出现空格、换行符等不可见字符"""
    return not any(c.isspace() for c in name)


def attribute_chinese_consistent_across_entities(name, standard_name):
    """(55)相同业务含义的属性，其中文名称应保持一致"""
    return name == standard_name


# 移除规范56（attribute_type_consistent_with_logical）相关调用

def relationship_name_english_chars(name):
    """(57)关系名称英文中只能包含下划线'_'、数字、字母"""
    return all(c.isalnum() or c == '_' for c in name)


def relationship_name_english_length(name):
    """(58)关系名称英文长度建议不超过30个字符"""
    return len(name) <= 30


def relationship_name_chinese_length(name):
    """(59)关系名称中文建议不超过15个汉字"""
    return len(name) <= 15


# ====================== 检查逻辑优化 ======================
def validate_excel_physical_model(excel_paths):
    from docx import Document
    doc = Document()  # 添加这行初始化代码
    HeCha = ""
    db_keywords = {"SELECT", "INSERT", "UPDATE", "DELETE", "FROM", "WHERE", "TABLE", "COLUMN", "INDEX", "VIEW",
                   "PRIMARY", "KEY", "FOREIGN", "CONSTRAINT"}  # 扩展关键字
    total_errors = []  # 存储所有错误

    for path in excel_paths:
        try:
            xls = pd.ExcelFile(path)
        except Exception as e:
            print(f"错误: 无法读取文件 {path}，原因: {str(e)}")
            continue

        # 处理物理模型检查（原逻辑保持不变）
        for sheet_name in xls.sheet_names:
            # 添加对特定sheet页的处理
            if "物理" in sheet_name.lower() or sheet_name in ["资产数据字典", "资源数据字典"]:
                df = xls.parse(sheet_name, keep_default_na=False, dtype=str)
                col_mapping = _get_sheet_col_mapping(sheet_name, df.columns)
                if not col_mapping:
                    continue

                # 物理表和字段检查（原逻辑）
                table_errors = _check_tables(df, col_mapping, db_keywords, sheet_name, path)
                field_errors = _check_fields(df, col_mapping, db_keywords, sheet_name, path)
                total_errors.extend(table_errors + field_errors)

        # 明确区分物理模型和逻辑模型检查
        # 修改validate_excel_physical_model函数中的sheet匹配逻辑
        for sheet_name in xls.sheet_names:
            sheet_clean = sheet_name.strip().lower()
            match = (
                    sheet_name == '物理模型字段'
                    or '物理' in sheet_clean
                    or '2.2物理模型字段' in sheet_clean
            )
            df = xls.parse(sheet_name, keep_default_na=False, dtype=str)
            col_mapping = _get_sheet_col_mapping(sheet_name, df.columns)
            if col_mapping:
                table_errors = _check_tables(df, col_mapping, db_keywords, sheet_name, path)
                field_errors = _check_fields(df, col_mapping, db_keywords, sheet_name, path)
                total_errors.extend(table_errors + field_errors)

            # 逻辑模型检查
            elif "逻辑属性" in sheet_clean or "实体属性" in sheet_clean:
                df = xls.parse(sheet_name, keep_default_na=False, dtype=str)
                # 修改列映射逻辑，适配项目中台
                col_mapping = {
                    "entity_en": "C" if ("逻辑属性" in sheet_clean) and "项目中台" in path else "B",
                    "entity_cn": "B" if ("逻辑属性" in sheet_clean) and "项目中台" in path else "A",
                    "attr_en": "E" if ("逻辑属性" in sheet_clean) and "项目中台" in path else "D",
                    "attr_cn": "D" if ("逻辑属性" in sheet_clean) and "项目中台" in path else "C",
                }

                # 根据不同文件和Sheet确定列索引映射
                col_mapping = {
                    "entity_en": "C" if ("实体属性" in sheet_name) and "财务中台" in path else "B",  # 实体英文名称
                    "entity_cn": "B" if ("实体属性" in sheet_name) and "财务中台" in path else "A",  # 实体中文名称
                    "attr_en": "E" if ("实体属性" in sheet_name) and "财务中台" in path else "D",  # 属性英文名称
                    "attr_cn": "D" if ("实体属性" in sheet_name) and "财务中台" in path else "C",  # 属性中文名称
                }

                # 转换列字母为索引（0-based）
                for key in col_mapping:
                    col_mapping[key] = _col_letter_to_index(col_mapping[key])

                # 检查实体英文名称（规范29-34）
                entity_en_errors = _check_entity_english(df, col_mapping["entity_en"], sheet_name, path, db_keywords)
                total_errors.extend(entity_en_errors)

                # 检查实体中文名称（规范35-40）
                entity_cn_errors = _check_entity_chinese(df, col_mapping["entity_cn"], sheet_name, path)
                total_errors.extend(entity_cn_errors)

                # 检查属性英文名称（规范41-48）
                attr_en_errors = _check_attribute_english(df, col_mapping["attr_en"], col_mapping["entity_en"],
                                                          sheet_name, path, db_keywords)
                total_errors.extend(attr_en_errors)

                # 检查属性中文名称（规范49-59）
                attr_cn_errors = _check_attribute_chinese(df, col_mapping["attr_cn"], col_mapping["entity_en"],
                                                          sheet_name, path)
                total_errors.extend(attr_cn_errors)

    # 统计结果（原输出逻辑保持不变）
    # 先进行去重处理
    seen_errors = set()
    unique_errors = []
    for error in total_errors:
        error_key = (error['file_path'], error['sheet_name'], error['row_num'],
                     error['entity_en'], error['value'], error['rule_code'])
        if error_key not in seen_errors:
            seen_errors.add(error_key)
            unique_errors.append(error)

    # 使用去重后的数据作为总数
    total_issues = len(unique_errors)
    issue_categories = len({error['rule_code'] for error in unique_errors}) if unique_errors else 0

    print(f"去重后共核查出 {total_issues} 个问题，共核查出 {issue_categories} 类")
    print("--------------------------------------------------")

    if unique_errors:
        # 后续处理使用unique_errors
        total_errors = unique_errors

        # Word文档中也使用去重后的数据
        doc.add_paragraph(f"共核查出 {total_issues} 个问题（已去重），涉及 {issue_categories} 类规范")
        errors_by_row = {}
        for error in total_errors:
            key = (error['file_path'], error['sheet_name'], error['row_num'])
            if key not in errors_by_row:
                errors_by_row[key] = []
            errors_by_row[key].append(error)

        # 在函数末尾添加以下代码
        from docx import Document

        # 创建Word文档
        doc = Document()
        doc.add_heading('数据模型核查结果', level=1)

        # 添加汇总信息
        doc.add_paragraph(f"共核查出 {total_issues} 个问题，涉及 {issue_categories} 类规范")
        doc.add_paragraph("详细问题如下：")

        # 按规范分类组织错误并统计数量
        errors_by_rule = {}
        for error in total_errors:
            if error['rule_code'] not in errors_by_rule:
                errors_by_rule[error['rule_code']] = []
            errors_by_rule[error['rule_code']].append(error)

        # 添加详细问题，按规范分类并显示数量
        for rule_code, errors in sorted(errors_by_rule.items(), key=lambda x: int(x[0].split()[-1])):
            doc.add_heading(f"规范{rule_code.split()[-1]} - {errors[0]['rule_desc']}（{len(errors)}）", level=2)

            for error in errors:
                doc.add_paragraph(f"工作表: {error['sheet_name']}")
                doc.add_paragraph(f"行号: {error['row_num'] + 1}")
                doc.add_paragraph(f"违规数据: '{error['value']}'")
                doc.add_paragraph("")

        # 保存到指定路径
        output_path = r"C:\Users\XX\Desktop\authbase\authbase1\authbase\uploads\数据标准化核查结果.docx"
        HeCha_content = "\n".join([p.text for p in doc.paragraphs])
        HeCha = HeCha_content
        doc.save(output_path)
        print(f"核查结果已保存到: {output_path}")
        print("核查结果变量HeCha内容:")
        print(HeCha)
    else:
        print("\n------------------------- 所有文件检查完毕 -------------------------")
    return(total_errors,HeCha)

# ====================== 新增逻辑模型检查函数 ======================
def _check_entity_english(df, col_index, sheet_name, file_path, db_keywords):
    """检查实体英文名称规范（29-34）"""
    errors = []
    # 先对数据进行去重处理
    unique_df = df.drop_duplicates(subset=[df.columns[col_index]])
    existing_names = set()

    for row_num, row in unique_df.iterrows():
        entity_en = row.iloc[col_index].strip() if isinstance(row.iloc[col_index], str) else ''
        current_row_num = row_num + 1

        if not entity_en:
            _log_error(errors, current_row_num, entity_en, "", "实体英文名称", entity_en, '39', "实体英文名称不允许为空", file_path,
                       sheet_name)
            continue

        # 规范29-34检查
        if not entity_english_camel_case(entity_en):
            _log_error(errors, current_row_num, entity_en, "", "实体英文名称", entity_en, '29', "实体英文名称采用驼峰命名规则，首字母大写",
                       file_path, sheet_name)
        if not entity_english_chars(entity_en):
            _log_error(errors, current_row_num, entity_en, "", "实体英文名称", entity_en, '30', "实体英文名称中只能包含数字、字母", file_path,
                       sheet_name)
        if not entity_english_no_keywords(entity_en, db_keywords):
            _log_error(errors, current_row_num, entity_en, "", "实体英文名称", entity_en, '31', "实体英文名称禁止直接使用数据库关键字或保留字",
                       file_path, sheet_name)
        if not entity_english_length(entity_en):
            _log_error(errors, current_row_num, entity_en, "", "实体英文名称", entity_en, '32', "实体英文名称长度建议不超过30个字符",
                       file_path, sheet_name)
        if not entity_english_no_invisible(entity_en):
            _log_error(errors, current_row_num, entity_en, "", "实体英文名称", entity_en, '33', "实体英文名称不允许出现空格、换行符等不可见字符",
                       file_path, sheet_name)
        if entity_en in existing_names:
            _log_error(errors, current_row_num, entity_en, "", "实体英文名称", entity_en, '34', "实体英文名称不允许重复", file_path,
                       sheet_name)
        existing_names.add(entity_en)

    return errors


def _check_entity_chinese(df, col_index, sheet_name, file_path):
    """检查实体中文名称规范（35-40）"""
    errors = []
    # 先对数据进行去重处理
    unique_df = df.drop_duplicates(subset=[df.columns[col_index]])
    existing_names = set()

    for row_num, row in unique_df.iterrows():
        entity_cn = row.iloc[col_index].strip() if isinstance(row.iloc[col_index], str) else ''
        current_row_num = row_num + 1

        if not entity_cn:
            _log_error(errors, current_row_num, "", entity_cn, "实体中文名称", entity_cn, '39', "实体中文名称不允许为空", file_path,
                       sheet_name)
            continue

        # 规范35-40检查
        if not entity_chinese_length(entity_cn):
            _log_error(errors, current_row_num, "", entity_cn, "实体中文名称", entity_cn, '35', "实体中文名称建议不超过15个汉字", file_path,
                       sheet_name)
        if not entity_chinese_phrase_type(entity_cn):
            _log_error(errors, current_row_num, "", entity_cn, "实体中文名称", entity_cn, '36', "实体中文名称应使用名词或者动宾短语",
                       file_path, sheet_name)
        if not entity_chinese_no_special_chars(entity_cn):
            _log_error(errors, current_row_num, "", entity_cn, "实体中文名称", entity_cn, '37', "实体中文名称不应出现特殊字符", file_path,
                       sheet_name)
        if not entity_chinese_no_invisible(entity_cn):
            _log_error(errors, current_row_num, "", entity_cn, "实体中文名称", entity_cn, '38', "实体中文名称不允许出现空格、换行符等不可见字符",
                       file_path, sheet_name)
        if entity_cn in existing_names:
            _log_error(errors, current_row_num, "", entity_cn, "实体中文名称", entity_cn, '40', "实体中文名称不允许重复", file_path,
                       sheet_name)
        existing_names.add(entity_cn)

    return errors


# 修正参数传递问题
def _check_attribute_english(df, attr_col_index, entity_col_index, sheet_name, file_path, db_keywords):
    """检查属性英文名称规范（41-48）"""
    errors = []
    entity_attr_map = {}  # 按实体分组的属性集合

    for row_num, row in df.iterrows():
        entity_en = row.iloc[entity_col_index].strip() if isinstance(row.iloc[entity_col_index], str) else ''
        attr_en = row.iloc[attr_col_index].strip() if isinstance(row.iloc[attr_col_index], str) else ''
        current_row_num = row_num + 1

        if not entity_en:
            entity_en = "未指定实体"

        if not attr_en:
            _log_error(errors, current_row_num, entity_en, "", "属性英文名称", attr_en, '46', "属性英文名称不允许为空", file_path,
                       sheet_name)
            continue

        # 初始化实体的属性集合
        if entity_en not in entity_attr_map:
            entity_attr_map[entity_en] = set()

        # 规范41-48检查
        if not attribute_english_first_char(attr_en):
            _log_error(errors, current_row_num, entity_en, "", "属性英文名称", attr_en, '41', "属性英文名称首字符只能为英文字母或下划线'_'",
                       file_path, sheet_name)
        if not attribute_english_chars(attr_en):
            _log_error(errors, current_row_num, entity_en, "", "属性英文名称", attr_en, '42', "属性英文名称中只能包含下划线'_'、数字、字母",
                       file_path, sheet_name)
        if not attribute_english_length(attr_en):
            _log_error(errors, current_row_num, entity_en, "", "属性英文名称", attr_en, '43', "属性英文名称长度建议不超过30个字符", file_path,
                       sheet_name)
        if not attribute_english_no_invisible(attr_en):
            _log_error(errors, current_row_num, entity_en, "", "属性英文名称", attr_en, '44', "属性英文名称不允许出现空格、换行符等不可见字符",
                       file_path, sheet_name)
        if not attribute_english_no_keywords(attr_en, db_keywords):
            _log_error(errors, current_row_num, entity_en, "", "属性英文名称", attr_en, '45', "属性英文名称禁止直接使用数据库关键字或保留字",
                       file_path, sheet_name)
        if attr_en in entity_attr_map[entity_en]:
            _log_error(errors, current_row_num, entity_en, "", "属性英文名称", attr_en, '47', "同一实体，属性英文名称不允许重复", file_path,
                       sheet_name)

        entity_attr_map[entity_en].add(attr_en)

    return errors

def _check_attribute_chinese(df, attr_col_index, entity_col_index, sheet_name, file_path):
    """检查属性中文名称规范（49-59）"""
    errors = []
    entity_attr_map = {}  # 按实体分组的属性集合

    for row_num, row in df.iterrows():
        entity_en = row.iloc[entity_col_index].strip() if isinstance(row.iloc[entity_col_index], str) else ''
        attr_cn = row.iloc[attr_col_index].strip() if isinstance(row.iloc[attr_col_index], str) else ''
        current_row_num = row_num + 1

        if not entity_en:
            entity_en = "未指定实体"

        if not attr_cn:
            _log_error(errors, current_row_num, entity_en, "", "属性中文名称", attr_cn, '51', "属性中文名称不允许为空", file_path,
                       sheet_name)
            continue

        # 初始化实体的属性集合
        if entity_en not in entity_attr_map:
            entity_attr_map[entity_en] = set()

        # 规范49-59检查
        if not attribute_chinese_length(attr_cn):
            _log_error(errors, current_row_num, entity_en, "", "属性中文名称", attr_cn, '49', "属性中文名称不应超过15个汉字", file_path,
                       sheet_name)
        if not attribute_chinese_phrase_type(attr_cn):
            _log_error(errors, current_row_num, entity_en, "", "属性中文名称", attr_cn, '50', "属性中文名称应使用名词或者动宾短语", file_path,
                       sheet_name)
        if not attribute_chinese_no_special_chars(attr_cn):
            _log_error(errors, current_row_num, entity_en, "", "属性中文名称", attr_cn, '53', "属性中文名称不应出现特殊字符", file_path,
                       sheet_name)
        if not attribute_chinese_no_invisible(attr_cn):
            _log_error(errors, current_row_num, entity_en, "", "属性中文名称", attr_cn, '54', "属性中文名称不允许出现空格、换行符等不可见字符",
                       file_path, sheet_name)
        if attr_cn in entity_attr_map[entity_en]:
            _log_error(errors, current_row_num, entity_en, "", "属性中文名称", attr_cn, '52', "同一实体，属性中文名称不允许重复", file_path,
                       sheet_name)

        entity_attr_map[entity_en].add(attr_cn)

    return errors

# ====================== 原有辅助函数 ======================
def _check_tables(df, col_mapping, db_keywords, sheet_name, file_path):
    """检查物理表名称规范（含行号记录，支持多规范违规记录）"""
    errors = []
    existing_table_en = set()  # 存储已检查过的英文表名
    existing_table_cn = set()  # 存储已检查过的中文表名

    # 先对数据进行严格去重
    df = df.drop_duplicates(subset=[col_mapping['table_en'], col_mapping['table_cn']], keep='first')

    for row_num, row in df.iterrows():
        table_en = row[col_mapping['table_en']].strip() if isinstance(row[col_mapping['table_en']], str) else ''
        table_cn = row[col_mapping['table_cn']].strip() if isinstance(row[col_mapping['table_cn']], str) else ''
        current_row_num = row_num + 1  # 行号从1开始
        table_errors = []  # 存储当前行的所有表级错误

        # 英文表名检查（规范1-6）
        if not table_en:
            table_errors.append(('1', "物理表英文名称不允许为空", table_en, "英文名称"))
        else:
            if not physical_table_english_first_char(table_en):
                table_errors.append(('1', "物理表英文名称首字符只能为英文字母或下划线'_'", table_en, "英文名称"))
            if not physical_table_english_chars(table_en):
                table_errors.append(('2', "物理表英文名称中只能包含下划线'_'、数字、字母", table_en, "英文名称"))
            if not physical_table_english_no_keywords(table_en, db_keywords):
                table_errors.append(('3', "物理表英文名称禁止直接使用数据库关键字或保留字", table_en, "英文名称"))
            if not physical_table_english_length(table_en):
                table_errors.append(('4', "物理表英文名称长度建议不超过30个字符", table_en, "英文名称"))
            if not physical_table_english_no_invisible(table_en):
                table_errors.append(('5', "物理表英文名称不允许出现空格、换行符等不可见字符", table_en, "英文名称"))
            # if table_en in existing_table_en:
            #     table_errors.append(('6', "物理表英文名称不允许重复", table_en, "英文名称"))
            # existing_table_en.add(table_en)

        # 中文表名检查（规范7-12）
        if not table_cn:
            table_errors.append(('11', "物理表中文名称不允许为空", table_cn, "中文名称"))
        else:
            if not physical_table_chinese_length(table_cn):
                table_errors.append(('7', "物理表中文名称建议不超过15个汉字", table_cn, "中文名称"))
            if not physical_table_chinese_phrase_type(table_cn):
                table_errors.append(('8', "物理表中文名称应使用名词或者动宾短语", table_cn, "中文名称"))
            if not physical_table_chinese_no_special_chars(table_cn):
                table_errors.append(('9', "物理表中文名称不应出现特殊字符", table_cn, "中文名称"))
            if not physical_table_chinese_no_invisible(table_cn):
                table_errors.append(('10', "物理表中文名称不允许出现空格、换行符等不可见字符", table_cn, "中文名称"))
            # if table_cn in existing_table_cn:
            #     table_errors.append(('12', "物理表中文名称不允许重复", table_cn, "中文名称"))
            # existing_table_cn.add(table_cn)

        # 将当前行的所有错误添加到结果列表
        for rule_code, rule_desc, value, field in table_errors:
            _log_error(errors, current_row_num, table_en, table_cn, field, value, rule_code, rule_desc, file_path,
                       sheet_name)

    return errors


def _check_fields(df, col_mapping, db_keywords, sheet_name, file_path):
    """检查字段规范（含行号记录，支持多规范违规记录）"""
    errors = []

    # 按表分组检查字段唯一性
    table_groups = df.groupby(col_mapping['table_en'])

    for table_en, table_group in table_groups:
        existing_field_en = set()  # 存储当前表已检查的英文字段名
        existing_field_cn = set()  # 存储当前表已检查的中文字段名

        for row_num, row in table_group.iterrows():
            table_cn = row[col_mapping['table_cn']].strip() if isinstance(row[col_mapping['table_cn']], str) else ''
            field_en = row[col_mapping['field_en']].strip() if isinstance(row[col_mapping['field_en']], str) else ''
            field_cn = re.sub(r'[\s\u3000\u00A0\u200b-\u200d\u2060\ufeff]+', '',
                              str(row[col_mapping['field_cn']])) if pd.notna(row[col_mapping['field_cn']]) else ''
            current_row_num = row_num + 1  # 行号从1开始
            field_errors = []  # 存储当前字段的所有错误

            # 跳过表名或字段名为空的行（已在表级检查中处理表名为空的情况）
            if not field_en:
                field_errors.append(('18', "字段英文名称不允许为空", field_en, "英文名称"))
            else:
                # 英文字段检查（规范13-20）
                if not field_english_first_char(field_en):
                    field_errors.append(('13', "字段英文名称首字符只能为英文字母或下划线'_'", field_en, "字段英文名称"))
                if not field_english_chars(field_en):
                    field_errors.append(('14', "字段英文名称中只能包含下划线'_'、数字、字母", field_en, "字段英文名称"))
                if not field_english_length(field_en):
                    field_errors.append(('15', "字段英文名称长度建议不超过30个字符", field_en, "字段英文名称"))
                if not field_english_no_invisible(field_en):
                    field_errors.append(('16', "字段英文名称不允许出现空格、换行符等不可见字符", field_en, "字段英文名称"))
                if not field_english_no_keywords(field_en, db_keywords):
                    field_errors.append(('17', "字段英文名称禁止直接使用数据库关键字或保留字", field_en, "字段英文名称"))
                # 检查字段唯一性（规范19）
                if field_en in existing_field_en:
                    field_errors.append(('19', "同一物理表，字段英文名称不允许重复", field_en, "字段英文名称"))
                existing_field_en.add(field_en)

            # 中文字段检查（规范21-27）
            if not field_cn:
                field_errors.append(('23', "字段中文名称不允许为空", field_cn, "字段中文名称"))
            else:
                if not field_chinese_length(field_cn):
                    field_errors.append(('21', "字段中文名称不应超过15个汉字", field_cn, "字段中文名称"))
                if not field_chinese_phrase_type(field_cn):
                    field_errors.append(('22', "字段中文名称应使用名词或者动宾短语", field_cn, "字段中文名称"))
                if not field_chinese_no_special_chars(field_cn):
                    field_errors.append(('25', "字段中文名称不应出现特殊字符", field_cn, "字段中文名称"))
                if not field_chinese_no_invisible(field_cn):
                    field_errors.append(('26', "字段中文名称不允许出现空格、换行符等不可见字符", field_cn, "字段中文名称"))
                # 检查字段唯一性（规范24）
                if field_cn in existing_field_cn:
                    field_errors.append(('24', "同一物理表，字段中文名称不允许重复", field_cn, "字段中文名称"))
                existing_field_cn.add(field_cn)

            # 将当前字段的所有错误添加到结果列表
            for rule_code, rule_desc, value, field in field_errors:
                _log_error(errors, current_row_num, table_en, table_cn, field, value, rule_code, rule_desc, file_path,
                           sheet_name)

    return errors


def _log_error(errors, row_num, entity_en, entity_cn, field_name, value, rule_code, rule_desc, file_path, sheet_name):
    # 检查是否已存在完全相同的错误记录
    duplicate = next((e for e in errors
                      if e['row_num'] == row_num
                      and e['entity_en'] == entity_en
                      and e['entity_cn'] == entity_cn
                      and e['field_name'] == field_name
                      and e['value'] == value
                      and e['rule_code'] == rule_code
                      and e['file_path'] == file_path
                      and e['sheet_name'] == sheet_name), None)

    if not duplicate:
        errors.append({
            'row_num': row_num,
            'entity_en': entity_en,
            'entity_cn': entity_cn,
            'field_name': field_name,
            'value': value,
            'rule_code': rule_code,
            'rule_desc': rule_desc,
            'file_path': file_path,
            'sheet_name': sheet_name
        })


def _get_sheet_col_mapping(sheet_name, columns):
    """根据工作表名称和列名获取列映射关系"""
    mappings = [
        # 资产数据字典/资源数据字典Sheet
        {
            "sheet_key": ["资产数据字典", "资源数据字典"],
            "columns": {
                "table_en": "表英文名称",
                "table_cn": "表中文注释",
                "field_en": "字段英文名称",
                "field_type": "字段数据类型",
                "field_cn": "字段中文注释"
            }
        },
        # 物理模型字段Sheet
        {
            "sheet_key": ["物理模型字段"],
            "columns": {
                "table_en": "物理英文名称",
                "table_cn": "物理中文名称",
                "field_en": "字段英文名称",
                "field_type": "字段类型",
                "field_cn": "字段中文名称"
            }
        },
        # 物理表字段Sheet
        {
            "sheet_key": ["物理表字段"],
            "columns": {
                "table_en": "表英文名称",
                "table_cn": "表中文名称",
                "field_en": "字段英文名称",
                "field_type": "类型",
                "field_cn": "字段中文名称"
            }
        }
    ]

    for mapping in mappings:
        if any(key in sheet_name for key in mapping["sheet_key"]):
            if set(mapping["columns"].values()).issubset(columns):
                return mapping["columns"]
    return None  # 未匹配到合适的列映射


def _get_rule_description(code):
    """获取规范描述信息"""
    descriptions = {
        '1': "物理表英文名称首字符只能为英文字母或下划线'_'",
        '2': "物理表英文名称中只能包含下划线'_'、数字、字母",
        '3': "物理表英文名称禁止直接使用数据库关键字或保留字",
        '4': "物理表英文名称长度建议不超过30个字符",
        '5': "物理表英文名称不允许出现空格、换行符等不可见字符",
        '6': "物理表英文名称不允许重复",
        '7': "物理表中文名称建议不超过15个汉字",
        '8': "物理表中文名称应使用名词或者动宾短语",
        '9': "物理表中文名称不应出现特殊字符",
        '10': "物理表中文名称不允许出现空格、换行符等不可见字符",
        '11': "物理表中文名称不允许为空",
        '12': "物理表中文名称不允许重复",
        '13': "字段英文名称首字符只能为英文字母或下划线'_'",
        '14': "字段英文名称中只能包含下划线'_'、数字、字母",
        '15': "字段英文名称长度建议不超过30个字符",
        '16': "字段英文名称不允许出现空格、换行符等不可见字符",
        '17': "字段英文名称禁止直接使用数据库关键字或保留字",
        '18': "字段英文名称不允许为空",
        '19': "同一物理表，字段英文名称不允许重复",
        '21': "字段中文名称不应超过15个汉字",
        '22': "字段中文名称应使用名词或者动宾短语",
        '23': "字段中文名称不允许为空",
        '24': "同一物理表，字段中文名称不允许重复",
        '25': "字段中文名称不应出现特殊字符",
        '26': "字段中文名称不允许出现空格、换行符等不可见字符",
        '27': "相同业务含义的字段，其中文名称应保持一致",
        '29': "实体英文名称采用驼峰命名规则，首字母大写",
        '30': "实体英文名称中只能包含数字、字母",
        '31': "实体英文名称禁止直接使用数据库关键字或保留字",
        '32': "实体英文名称长度建议不超过30个字符",
        '33': "实体英文名称不允许出现空格、换行符等不可见字符",
        '34': "实体英文名称不允许重复",
        '35': "实体中文名称建议不超过15个汉字",
        '36': "实体中文名称应使用名词或者动宾短语",
        '37': "实体中文名称不应出现特殊字符",
        '38': "实体中文名称不允许出现空格、换行符等不可见字符",
        '39': "实体中文名称不允许为空",
        '40': "实体中文名称不允许重复",
        '41': "属性英文名称首字符只能为英文字母或下划线'_'",
        '42': "属性英文名称中只能包含下划线'_'、数字、字母",
        '43': "属性英文名称长度建议不超过30个字符",
        '44': "属性英文名称不允许出现空格、换行符等不可见字符",
        '45': "属性英文名称不允许为空",
        '46': "属性英文名称不允许为空",
        '47': "同一实体，属性英文名称不允许重复",
        '49': "属性中文名称不应超过15个汉字",
        '50': "属性中文名称应使用名词或者动宾短语",
        '51': "属性中文名称不允许为空",
        '52': "同一实体，属性中文名称不允许重复",
        '53': "属性中文名称不应出现特殊字符",
        '54': "属性中文名称不允许出现空格、换行符等不可见字符",
    }
    return descriptions.get(code, f"未知规范 [{code}]")


def _col_letter_to_index(col_letter):
    """将Excel列字母转换为0-based索引"""
    index = 0
    for char in col_letter:
        index = index * 26 + (ord(char.upper()) - ord('A') + 1)
    return index - 1  # 转换为0-based索引


# ====================== 执行检查 ======================
if __name__ == "__main__":
    # 定义要检查的Excel文件路径
    excel_paths = [
        r"D:\业务中台\17  业务中台模型工作对接\03 项目中台2025年模型抽查工作\202505\基线表_项目中台数据模型2.xlsx"
        # r"D:\业务中台-------杨金荣老师对接@@@@@@@@@@@@@@@@@@@@@@@@@@\17  业务中台模型工作对接250529\04 财务中台2025年模型抽查工作\2025年05月\溯源明细 - 副本.xlsx"
        # r"C:\Users\XX\Desktop\3大中台\项目中台4月模型基线变更\25年4月项目中台数据模型基线2.xlsx"
    ]
    # 执行检查
    validate_excel_physical_model(excel_paths)
    print("\n------------------------- 所有文件检查完毕 -------------------------")

